﻿using System;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
using Workbench.Operations;
using Workbench.Shared;

namespace Workbench.Presentation
{
    public class LemmatizerViewModel : ProcessWordsViewViewModelBase
    {
        #region Properties

        private ObservableCollection<BaseFormViewModel> currentBaseForms = new ObservableCollection<BaseFormViewModel>();
        public ObservableCollection<BaseFormViewModel> CurrentBaseForms { get { return currentBaseForms; } }

        private BaseFormViewModel selectedBaseForm;
        public BaseFormViewModel SelectedBaseForm
        {
            get { return selectedBaseForm; }
            set
            {
                if (selectedBaseForm != value)
                {
                    selectedBaseForm = value;
                    RaisePropertyChanged();
                }
            }
        }

        private bool hasSelectionBaseForm;
        public bool HasSelectionBaseForm
        {
            get { return hasSelectionBaseForm; }
            set { hasSelectionBaseForm = value; RaisePropertyChanged(); }
        }

        private string currentBaseFormContent;
        public string CurrentBaseFormContent
        {
            get { return currentBaseFormContent; }
            set
            {
                currentBaseFormContent = value;
                RaisePropertyChanged();
            }
        }

        #endregion Properties

        #region Constructors

        public LemmatizerViewModel()
            : base("Lemmatizer")
        {
            CurrentBaseForms.SubscribeItemPropChange("IsSelected", CurrentBaseFormSuggestions_IsSelectedChanged);
            this.PropertyChanged += LemmatizerViewModel_PropertyChanged;
            WordsListViewModel.PropertyChanged += WordsListViewModel_PropertyChanged;

            setBaseFormCommand = new _SetBaseFormCommand(this);
            unSetBaseFormCommand = new _UnSetBaseFormCommand(this);
            addBaseFormCommand = new _AddBaseFormCommand(this);
            removeBaseFormCommand = new _RemoveBaseFormCommand(this);
        }

        #endregion

        #region Event Handlers

        void WordsListViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (string.Compare(e.PropertyName, "LastSelectedWord", false) == 0)
            {
                var vm = sender as ProcessWordsListViewModel<WordViewModelBase>;
                if (vm.LastSelectedWord == null) return;

                CurrentBaseFormContent = vm.LastSelectedWord.BaseForm;
            }
        }

        void CurrentBaseFormSuggestions_IsSelectedChanged(BaseFormViewModel baseFormViewModel)
        {
            if (baseFormViewModel.IsSelected)
            {
                SelectedBaseForm = baseFormViewModel;
            }
        }

        void LemmatizerViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            switch (e.PropertyName)
            {
                case "CurrentBaseFormContent": ShowBaseFormSuggestions(); break;
            }
        }

        public override void Unload()
        {
            this.CurrentBaseFormContent = null;
            this.CurrentBaseForms.Clear();
            this.FileSelectViewModel.Unload();
            this.HasSelectedFile = false;
            this.HasSelectionBaseForm = false;
            this.SelectedBaseForm = null;
            this.SentenceViewModel.Unload();
            this.WordsListViewModel.Unload();
        }

        public override void Load()
        {
            var rootDirectory = ProjectOperations.GetRootDirectory();
            FileSelectViewModel.ShowFiles(rootDirectory);

            CurrentBaseFormContent = ""; // fetch suggestions
        }

        #endregion Event Handlers

        #region Commands

        #region SetBaseFormCommand

        readonly _SetBaseFormCommand setBaseFormCommand;
        public ICommand SetBaseFormCommand
        {
            get { return setBaseFormCommand; }
        }

        private class _SetBaseFormCommand : ICommand
        {
            readonly LemmatizerViewModel viewModel;

            public _SetBaseFormCommand(LemmatizerViewModel viewModel)
            {
                this.viewModel = viewModel;

                viewModel.WordsListViewModel.SelectedWords.CollectionChanged += SelectedWords_CollectionChanged;
                viewModel.PropertyChanged += viewModel_PropertyChanged;
            }

            void SelectedWords_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
            {
                RaiseCanExecuteChanged();
            }

            void viewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
            {
                if (string.Compare(e.PropertyName, "SelectedBaseForm", false) == 0)
                {
                    RaiseCanExecuteChanged();
                }
            }

            void RaiseCanExecuteChanged()
            {
                this.CanExecuteChanged(null, EventArgs.Empty);
            }

            public bool CanExecute(object parameter)
            {
                return viewModel.WordsListViewModel.SelectedWords.Count > 0 && viewModel.SelectedBaseForm != null;
            }

            public void Execute(object parameter)
            {
                switch (viewModel.OperationContext)
                {
                    case OperationContext.Selection:
                        LemmatizerOperations.UpdateBaseFormForWords(viewModel.SelectedBaseForm.Content, 
                            viewModel.WordsListViewModel.SelectedWords.Select(x => x.DataModel));
                        break;
                    case OperationContext.CurrentFile:
                        {
                            var uniqueWordId = viewModel.WordsListViewModel.LastSelectedWord.UniqueWordId;
                            var fileId = viewModel.FileSelectViewModel.SelectedFile.RowId;
                            var baseFormId = viewModel.SelectedBaseForm.DataModel.RowId;
                            LemmatizerOperations.UpdateBaseFormForWordsInFile(baseFormId, uniqueWordId, fileId);
                            var words = viewModel.WordsListViewModel
                                                .AvailableWords
                                                .Where(x => x.UniqueWordId == uniqueWordId)
                                                .Cast<LemmatizerWordViewModel>();
                            foreach (var word in words)
                            {
                                word.DataModel.BaseForm = viewModel.SelectedBaseForm.DataModel;
                            }
                            break;
                        }
                    case OperationContext.AllFiles:
                        {
                            var uniqueWordId = viewModel.WordsListViewModel.LastSelectedWord.UniqueWordId;
                            var baseFormId = viewModel.SelectedBaseForm.DataModel.RowId;
                            LemmatizerOperations.UpdateBaseFormForWordsInAllFiles(baseFormId, uniqueWordId);
                            var words = viewModel.WordsListViewModel
                                                .AvailableWords
                                                .Where(x => x.UniqueWordId == uniqueWordId)
                                                .Cast<LemmatizerWordViewModel>();
                            foreach (var word in words)
                            {
                                word.DataModel.BaseForm = viewModel.SelectedBaseForm.DataModel;
                            }
                            break;
                        }
                }
            }

            public event EventHandler CanExecuteChanged = (sender, args) => { };
        }

        #endregion SetBaseFormCommand

        #region UnSetBaseFormCommand

        readonly _UnSetBaseFormCommand unSetBaseFormCommand;
        public ICommand UnSetBaseFormCommand
        {
            get { return unSetBaseFormCommand; }
        }

        private class _UnSetBaseFormCommand : ICommand
        {
            readonly LemmatizerViewModel viewModel;

            public _UnSetBaseFormCommand(LemmatizerViewModel viewModel)
            {
                this.viewModel = viewModel;

                viewModel.WordsListViewModel.SelectedWords.CollectionChanged += SelectedWords_CollectionChanged;
                viewModel.WordsListViewModel.CurrentWords.SubscribeItemPropChange("IsSet", CurrentWords_IsSetChanged);
            }

            void SelectedWords_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
            {
                RaiseCanExecuteChanged();
            }

            void CurrentWords_IsSetChanged(WordViewModelBase model)
            {
                RaiseCanExecuteChanged();
            }

            void RaiseCanExecuteChanged()
            {
                this.CanExecuteChanged(null, EventArgs.Empty);
            }

            public bool CanExecute(object parameter)
            {
                return viewModel.WordsListViewModel.SelectedWords.Any(x => x.IsSet);
            }

            public void Execute(object parameter)
            {
                switch (viewModel.OperationContext)
                {
                    case OperationContext.Selection:
                        LemmatizerOperations.ClearBaseFormForWords(viewModel.WordsListViewModel.SelectedWords.Select(x => x.DataModel));
                        break;
                    case OperationContext.CurrentFile:
                        {
                            var uniqueWordId = viewModel.WordsListViewModel.LastSelectedWord.UniqueWordId;
                            var fileId = viewModel.FileSelectViewModel.SelectedFile.RowId;
                            LemmatizerOperations.ClearBaseFormForWordsInFile(uniqueWordId, fileId);
                            var words = viewModel.WordsListViewModel
                                                .AvailableWords
                                                .Where(x => x.UniqueWordId == uniqueWordId)
                                                .Cast<LemmatizerWordViewModel>();
                            foreach (var word in words)
                            {
                                word.DataModel.BaseForm = null;
                            }
                            break;
                        }
                    case OperationContext.AllFiles:
                        {
                            var uniqueWordId = viewModel.WordsListViewModel.LastSelectedWord.UniqueWordId;
                            LemmatizerOperations.ClearBaseFormForWordsInAllFiles(uniqueWordId);
                            var words = viewModel.WordsListViewModel
                                                .AvailableWords
                                                .Where(x => x.UniqueWordId == uniqueWordId)
                                                .Cast<LemmatizerWordViewModel>();
                            foreach (var word in words)
                            {
                                word.DataModel.BaseForm = null;
                            }
                            break;
                        }
                }
            }

            public event EventHandler CanExecuteChanged = (sender, args) => { };
        }

        #endregion UnSetBaseFormCommand

        #region AddBaseFormCommand

        readonly _AddBaseFormCommand addBaseFormCommand;
        public ICommand AddBaseFormCommand
        {
            get { return addBaseFormCommand; }
        }

        private class _AddBaseFormCommand : ICommand
        {
            readonly LemmatizerViewModel viewModel;

            public _AddBaseFormCommand(LemmatizerViewModel viewModel)
            {
                this.viewModel = viewModel;
                viewModel.PropertyChanged += viewModel_PropertyChanged;
            }

            void viewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
            {
                if (string.Compare(e.PropertyName, "CurrentBaseFormContent", false) == 0)
                {
                    RaiseCanExecuteChanged();
                }
            }

            public bool CanExecute(object parameter)
            {
                return !string.IsNullOrWhiteSpace(viewModel.CurrentBaseFormContent)
                    && viewModel.CurrentBaseForms
                                .FirstOrDefault(x => string.Compare(x.Content, viewModel.CurrentBaseFormContent, false) == 0) == null;
            }

            public void RaiseCanExecuteChanged()
            {
                this.CanExecuteChanged(null, EventArgs.Empty);
            }

            public void Execute(object parameter)
            {
                Debug.Assert(!string.IsNullOrWhiteSpace(viewModel.CurrentBaseFormContent));

                var newBaseForm = LemmatizerOperations.AddBaseForm(viewModel.CurrentBaseFormContent);
                var vmNewBaseForm = new BaseFormViewModel(newBaseForm);
                viewModel.CurrentBaseForms.Insert(0, vmNewBaseForm);

                if (viewModel.SelectedBaseForm != null)
                {
                    viewModel.SelectedBaseForm.IsSelected = false;
                }
                vmNewBaseForm.IsSelected = true;
            }

            public event EventHandler CanExecuteChanged = (sender, args) => { };
        }

        #endregion AddBaseFormCommand

        #region RemoveBaseFormCommand

        readonly _RemoveBaseFormCommand removeBaseFormCommand;
        public ICommand RemoveBaseFormCommand
        {
            get { return removeBaseFormCommand; }
        }

        private class _RemoveBaseFormCommand : ICommand
        {
            readonly LemmatizerViewModel viewModel;

            public _RemoveBaseFormCommand(LemmatizerViewModel viewModel)
            {
                this.viewModel = viewModel;
                viewModel.PropertyChanged += viewModel_PropertyChanged;
            }

            void viewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
            {
                if (string.Compare(e.PropertyName, "SelectedBaseForm", false) == 0)
                {
                    RaiseCanExecuteChanged();
                }
            }

            public bool CanExecute(object parameter)
            {
                var selectedBaseForm = viewModel.SelectedBaseForm;
                return selectedBaseForm != null
                    && selectedBaseForm.DataModel != null
                    && selectedBaseForm.DataModel.IsPersisted();
            }

            public void RaiseCanExecuteChanged()
            {
                this.CanExecuteChanged(null, EventArgs.Empty);
            }

            public void Execute(object parameter)
            {
                var vmBaseForm = viewModel.SelectedBaseForm;
                var baseForm = vmBaseForm.DataModel;

                var wordsToUpdate = viewModel.WordsListViewModel.AvailableWords
                    .Where(x => x.DataModel.BaseForm == baseForm)
                    .Select(x => x.DataModel);

                LemmatizerOperations.DeleteBaseForm(baseForm, wordsToUpdate);
                viewModel.CurrentBaseForms.Remove(vmBaseForm);
                viewModel.SelectedBaseForm = null;
            }

            public event EventHandler CanExecuteChanged = (sender, args) => { };
        }

        #endregion RemoveBaseFormCommand

        #endregion

        #region Actions

        public void ShowBaseFormSuggestions()
        {
            var suggestions = LemmatizerOperations.GetBaseFormSuggestions(CurrentBaseFormContent)
                                                .Select(x => new BaseFormViewModel(x));
            SelectedBaseForm = null;
            currentBaseForms.Clear();
            CurrentBaseForms.AddRange(suggestions);
        }

        public override async Task LoadFile(ProjectFileViewModel fileViewModel)
        {
            HasSelectedFile = false;
            CurrentBaseFormContent = null;
            CurrentBaseForms.Clear();
            WordsListViewModel.Unload();
            SentenceViewModel.Unload();

            if (fileViewModel != null)
            {
                var words = await LemmatizerOperations.GetWordsAsync(fileViewModel.RowId);
                var wordViewModels = words.Select(x => new LemmatizerWordViewModel(x))
                                            .Cast<WordViewModelBase>()
                                            .OrderBy(x => x.Content);
                WordsListViewModel.ShowWords(wordViewModels);
                HasSelectedFile = true;
            }
        }

        #endregion
    }
}
